wayland: Move popups with xdg_popup.reposition
authorJonas Ådahl <jadahl@gmail.com>
Sun, 16 Feb 2020 19:09:42 +0000 (20:09 +0100)
committerJonas Ådahl <jadahl@gmail.com>
Wed, 8 Apr 2020 21:32:47 +0000 (23:32 +0200)
The third version of xdg-shell introduces support for explicit popup
repositioning. If available, make use of this to implement popup
repositioning.

Note that this does *NOT* include atomic parent-child state
synchronization. For that,
https://gitlab.freedesktop.org/wayland/wayland-protocols/issues/13 will
be needed.

This currently uses my own fork of wayland-protocols which adds meson
support, so that we can use it as a subproject. Eventually when
wayland-protocols' meson support lands upstream, we should change it to
point there.

Silence some meson warnings while at it to make CI happy.

This also bumps the glib requirement, since g_warning_once() is used.

gdk/wayland/gdkdisplay-wayland.c
gdk/wayland/gdkdisplay-wayland.h
gdk/wayland/gdksurface-wayland.c
gdk/wayland/meson.build
gtk/meson.build
meson.build
subprojects/wayland-protocols.wrap [new file with mode: 0644]

index 76596da719cdc6bda0464996583890e00ac3b4f1..dc8c52f305f3f9d8ab28d95301ec2de5f6a32473 100644 (file)
@@ -431,6 +431,7 @@ gdk_registry_handle_global (void               *data,
   else if (strcmp (interface, "xdg_wm_base") == 0)
     {
       display_wayland->xdg_wm_base_id = id;
+      display_wayland->xdg_wm_base_version = version;
     }
   else if (strcmp (interface, "zxdg_shell_v6") == 0)
     {
@@ -659,7 +660,8 @@ _gdk_wayland_display_open (const gchar *display_name)
       display_wayland->xdg_wm_base =
         wl_registry_bind (display_wayland->wl_registry,
                           display_wayland->xdg_wm_base_id,
-                          &xdg_wm_base_interface, 1);
+                          &xdg_wm_base_interface,
+                          MIN (display_wayland->xdg_wm_base_version, 3));
       xdg_wm_base_add_listener (display_wayland->xdg_wm_base,
                                 &xdg_wm_base_listener,
                                 display_wayland);
index 4313ac3b17683abd5a1566a243c1e2ecb5607ee5..db4b3efe4152bdc441f033aaa2dea8867c026273 100644 (file)
@@ -86,6 +86,7 @@ struct _GdkWaylandDisplay
   guint32 serial;
 
   uint32_t xdg_wm_base_id;
+  int xdg_wm_base_version;
   uint32_t zxdg_shell_v6_id;
   GdkWaylandShellVariant shell_variant;
 
index 6947fe0b9e98d6c482e3644e9b6338d0a04edf8c..5e734e6ecdbaca67e8dd74d677c756b5d6b80cf3 100644 (file)
@@ -59,6 +59,7 @@ static guint signals[LAST_SIGNAL];
 typedef enum _PopupState
 {
   POPUP_STATE_IDLE,
+  POPUP_STATE_WAITING_FOR_REPOSITIONED,
   POPUP_STATE_WAITING_FOR_CONFIGURE,
   POPUP_STATE_WAITING_FOR_FRAME,
 } PopupState;
@@ -94,6 +95,9 @@ struct _GdkWaylandSurface
   EGLSurface egl_surface;
   EGLSurface dummy_egl_surface;
 
+  uint32_t reposition_token;
+  uint32_t received_reposition_token;
+
   PopupState popup_state;
 
   unsigned int initial_configure_received : 1;
@@ -167,12 +171,18 @@ struct _GdkWaylandSurface
       int y;
       int width;
       int height;
+      uint32_t repositioned_token;
+      gboolean has_repositioned_token;
     } popup;
 
+    gboolean is_initial_configure;
+
     uint32_t serial;
     gboolean is_dirty;
   } pending;
 
+  uint32_t last_configure_serial;
+
   int state_freeze_count;
 
   struct {
@@ -438,6 +448,7 @@ frame_callback (void               *data,
   switch (impl->popup_state)
     {
     case POPUP_STATE_IDLE:
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
     case POPUP_STATE_WAITING_FOR_CONFIGURE:
       break;
     case POPUP_STATE_WAITING_FOR_FRAME:
@@ -1282,8 +1293,17 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface)
                                      impl->pending.serial);
     }
 
+  if (impl->pending.popup.has_repositioned_token)
+    impl->received_reposition_token = impl->pending.popup.repositioned_token;
+
   switch (impl->popup_state)
     {
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+      if (impl->received_reposition_token != impl->reposition_token)
+        return;
+      else
+        gdk_surface_thaw_updates (surface);
+      G_GNUC_FALLTHROUGH;
     case POPUP_STATE_WAITING_FOR_CONFIGURE:
       impl->popup_state = POPUP_STATE_WAITING_FOR_FRAME;
       break;
@@ -1306,6 +1326,10 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface)
                              width, height,
                              impl->popup.layout);
 
+  if (!impl->pending.popup.has_repositioned_token &&
+      !impl->pending.is_initial_configure)
+    g_signal_emit_by_name (surface, "popup-layout-changed");
+
   gdk_surface_invalidate_rect (surface, NULL);
 }
 
@@ -1318,6 +1342,7 @@ gdk_wayland_surface_configure (GdkSurface *surface)
     {
       gdk_surface_thaw_updates (surface);
       impl->initial_configure_received = TRUE;
+      impl->pending.is_initial_configure = TRUE;
     }
 
   if (is_realized_popup (surface))
@@ -1327,6 +1352,8 @@ gdk_wayland_surface_configure (GdkSurface *surface)
   else
     g_warn_if_reached ();
 
+  impl->last_configure_serial = impl->pending.serial;
+
   memset (&impl->pending, 0, sizeof (impl->pending));
 }
 
@@ -1663,9 +1690,31 @@ xdg_popup_done (void             *data,
   gdk_surface_hide (surface);
 }
 
+static void
+xdg_popup_repositioned (void             *data,
+                        struct xdg_popup *xdg_popup,
+                        uint32_t          token)
+{
+  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  GDK_DISPLAY_NOTE (gdk_surface_get_display (surface), EVENTS,
+                    g_message ("repositioned %p", surface));
+
+  if (impl->popup_state != POPUP_STATE_WAITING_FOR_REPOSITIONED)
+    {
+      g_warning ("Unexpected xdg_popup.repositioned event, probably buggy compositor");
+      return;
+    }
+
+  impl->pending.popup.repositioned_token = token;
+  impl->pending.popup.has_repositioned_token = TRUE;
+}
+
 static const struct xdg_popup_listener xdg_popup_listener = {
   xdg_popup_configure,
   xdg_popup_done,
+  xdg_popup_repositioned,
 };
 
 static void
@@ -2052,7 +2101,8 @@ static gpointer
 create_dynamic_positioner (GdkSurface     *surface,
                            int             width,
                            int             height,
-                           GdkPopupLayout *layout)
+                           GdkPopupLayout *layout,
+                           gboolean        ack_parent_configure)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
   GdkSurface *parent = surface->parent;
@@ -2129,6 +2179,30 @@ create_dynamic_positioner (GdkSurface     *surface,
         xdg_positioner_set_constraint_adjustment (positioner,
                                                   constraint_adjustment);
 
+        if (xdg_positioner_get_version (positioner) >=
+            XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION)
+          xdg_positioner_set_reactive (positioner);
+
+        if (ack_parent_configure &&
+            xdg_positioner_get_version (positioner) >=
+            XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION)
+          {
+            GdkWaylandSurface *parent_impl = GDK_WAYLAND_SURFACE (parent);
+            int parent_width;
+            int parent_height;
+
+            parent_width = parent->width - (parent_impl->margin_left +
+                                            parent_impl->margin_right);
+            parent_height = parent->height - (parent_impl->margin_top +
+                                              parent_impl->margin_bottom);
+
+            xdg_positioner_set_parent_size (positioner,
+                                            parent_width,
+                                            parent_height);
+            xdg_positioner_set_parent_configure (positioner,
+                                                 parent_impl->last_configure_serial);
+          }
+
         return positioner;
       }
     case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
@@ -2233,7 +2307,7 @@ gdk_wayland_surface_create_xdg_popup (GdkSurface     *surface,
 
   gdk_surface_freeze_updates (surface);
 
-  positioner = create_dynamic_positioner (surface, width, height, layout);
+  positioner = create_dynamic_positioner (surface, width, height, layout, FALSE);
 
   switch (display->shell_variant)
     {
@@ -2501,6 +2575,9 @@ gdk_wayland_surface_hide_surface (GdkSurface *surface)
         {
           switch (impl->popup_state)
             {
+            case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+              gdk_surface_thaw_updates (surface);
+              G_GNUC_FALLTHROUGH;
             case POPUP_STATE_WAITING_FOR_CONFIGURE:
             case POPUP_STATE_WAITING_FOR_FRAME:
               thaw_popup_toplevel_state (surface);
@@ -2602,6 +2679,7 @@ do_queue_relayout (GdkSurface     *surface,
                    GdkPopupLayout *layout)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  struct xdg_positioner *positioner;
 
   g_assert (is_realized_popup (surface));
   g_assert (impl->popup_state == POPUP_STATE_IDLE ||
@@ -2612,7 +2690,41 @@ do_queue_relayout (GdkSurface     *surface,
   impl->popup.unconstrained_width = width;
   impl->popup.unconstrained_height = height;
 
-  queue_relayout_fallback (surface, layout);
+  if (!impl->display_server.xdg_popup ||
+      xdg_popup_get_version (impl->display_server.xdg_popup) <
+      XDG_POPUP_REPOSITION_SINCE_VERSION)
+    {
+      g_warning_once ("Compositor doesn't support moving popups, "
+                      "relying on remapping");
+      queue_relayout_fallback (surface, layout);
+
+      return;
+    }
+
+  positioner = create_dynamic_positioner (surface,
+                                          width, height, layout,
+                                          TRUE);
+  xdg_popup_reposition (impl->display_server.xdg_popup,
+                        positioner,
+                        ++impl->reposition_token);
+  xdg_positioner_destroy (positioner);
+
+  gdk_surface_freeze_updates (surface);
+
+  switch (impl->popup_state)
+    {
+    case POPUP_STATE_IDLE:
+      freeze_popup_toplevel_state (surface);
+      break;
+    case POPUP_STATE_WAITING_FOR_FRAME:
+      break;
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+    default:
+      g_assert_not_reached ();
+    }
+
+  impl->popup_state = POPUP_STATE_WAITING_FOR_REPOSITIONED;
 }
 
 static gboolean
@@ -2623,6 +2735,9 @@ is_relayout_finished (GdkSurface *surface)
   if (!impl->initial_configure_received)
     return FALSE;
 
+  if (impl->reposition_token != impl->received_reposition_token)
+    return FALSE;
+
   return TRUE;
 }
 
@@ -2707,6 +2822,7 @@ reposition_popup (GdkSurface     *surface,
     case POPUP_STATE_WAITING_FOR_FRAME:
       do_queue_relayout (surface, width, height, layout);
       break;
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
     case POPUP_STATE_WAITING_FOR_CONFIGURE:
       g_warn_if_reached ();
       break;
index b6592064fba198c453abfeae90c48a35bb3444bd..14267f4ccc007625ac47fe45410d61f27918335d 100644 (file)
@@ -37,10 +37,6 @@ gdk_wayland_deps = [
   wlegldep,
 ]
 
-# wayland protocols
-proto_dir = dependency('wayland-protocols').get_pkgconfig_variable('pkgdatadir')
-assert(proto_dir != '', 'Could not get pkgdatadir from wayland-protocols.pc')
-
 wayland_scanner = find_program('wayland-scanner')
 
 # Format:
@@ -68,14 +64,14 @@ foreach p: proto_sources
 
   if proto_stability == 'stable'
     output_base = proto_name
-    input = join_paths(proto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base))
+    input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
   elif proto_stability == 'private'
     output_base = proto_name
-    input = 'protocol/@0@.xml'.format(proto_name)
+    input = files('protocol/@0@.xml'.format(proto_name))
   else
     proto_version = p.get(2)
     output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability, proto_version)
-    input = join_paths(proto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base))
+    input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
   endif
 
   gdk_wayland_gen_headers += custom_target('@0@ client header'.format(output_base),
index c7627af5f06d0deebf0c2d140dc59ca593035d28..28e23b6781a6c0615d6005e58b48974d9d019b3a 100644 (file)
@@ -644,11 +644,11 @@ foreach p: proto_sources
   if wayland_enabled
     if proto_stability == 'stable'
       output_base = proto_name
-      input = '@0@.xml'.format(proto_name)
+      input = files('@0@.xml'.format(proto_name))
     else
       proto_version = p.get(2)
       output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability, proto_version)
-      input = join_paths(proto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base))
+      input = files(join_paths(wlproto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)))
     endif
 
     # wayland_scanner is defined in gdk/wayland/meson.build
index d3b94574ab929347f765aa54eac6cae3c0d630e8..ecf4228b400a0903662924ee2b874b01fd841871 100644 (file)
@@ -11,8 +11,8 @@ project('gtk', 'c',
         license: 'LGPLv2.1+')
 
 glib_major_req = 2
-glib_minor_req = 59
-glib_micro_req = 0
+glib_minor_req = 63
+glib_micro_req = 1
 
 if glib_minor_req.is_odd()
   glib_min_required = 'GLIB_VERSION_@0@_@1@'.format(glib_major_req, glib_minor_req - 1)
@@ -33,7 +33,7 @@ atk_req            = '>= 2.15.1'
 cairo_req          = '>= 1.14.0'
 gdk_pixbuf_req     = '>= 2.30.0'
 introspection_req  = '>= 1.39.0'
-wayland_proto_req  = '>= 1.14'
+wayland_proto_req  = '>= 1.20'
 wayland_req        = '>= 1.14.91'
 graphene_req       = '>= 1.9.1'
 epoxy_req          = '>= 1.4'
@@ -469,10 +469,16 @@ atk_pkgs = ['atk']
 wayland_pkgs = []
 if wayland_enabled
   wlclientdep    = dependency('wayland-client', version:  wayland_req)
-  wlprotocolsdep = dependency('wayland-protocols', version: wayland_proto_req)
+  wlprotocolsdep = dependency('wayland-protocols', version: wayland_proto_req, required: false)
   wlegldep       = dependency('wayland-egl')
   backend_immodules += ['wayland']
 
+  if not wlprotocolsdep.found()
+    wlproto_dir = subproject('wayland-protocols').get_variable('wayland_protocols_srcdir')
+  else
+    wlproto_dir = wlprotocolsdep.get_pkgconfig_variable('pkgdatadir')
+  endif
+
   wayland_pkgs = [
     'wayland-client', wayland_req,
     'wayland-protocols', wayland_proto_req,
diff --git a/subprojects/wayland-protocols.wrap b/subprojects/wayland-protocols.wrap
new file mode 100644 (file)
index 0000000..bb9619c
--- /dev/null
@@ -0,0 +1,4 @@
+[wrap-git]
+directory=wayland-protocols
+url=https://gitlab.freedesktop.org/jadahl/wayland-protocols.git
+revision=wip/meson-meson-0.53